#include <fstream>
#include <iostream>
#include "ns3/log.h"
#include "ns3/simulator.h"
#include "wfq-in-netdevice.h"
#include "ns3/error-model.h"
#include "ns3/ethernet-header.h"
#include "ns3/ethernet-trailer.h"
#define PPP 2
#define CSMA 14


namespace ns3 {

NS_LOG_COMPONENT_DEFINE("H_WFQQueue");

NS_OBJECT_ENSURE_REGISTERED(H_WFQQueue);

TypeId H_WFQQueue::GetTypeId(void) {
  static TypeId tid = TypeId("ns3::H_WFQQueue")
    .SetParent<Queue>()
    .SetGroupName("Internet")
    .AddConstructor<H_WFQQueue>()
    ;
    return tid;
  }

/*
 * \In constructor function, there pass a variable named type,
 * it is used to tell the node type to which the WFQ belongs.
 * if type is 1, means the WFQ belongs to a switch node,
 * and if type is 0, means the WFQ belongs to a host node.
 *
 * \m_threshold is the threshold to attach ecn tag to packet, its initial value is 0.5,
 * and its value ranges from 0 to 1.
 *
 * \m_limit is the length limit of inner queue, its initial value is 1000.
 *
 * \m_linkRate is the link rate used in WFQ algorithm, its initial value is 500 bps.
 *
 * \function CreateQueueWithWeight is called to create an inner queue with weight value 1 initially.
 * 
 * \m_map is used to set mapping rule from tos 0 to queue id 0 initially.
 */
H_WFQQueue::H_WFQQueue(uint8_t type) : 
    Queue(),
    m_packets()
{
  NS_LOG_FUNCTION(this);
  CreateQueueWithWeight(1);
  m_threshold = 0.5;
  m_limit = 1000;
  m_linkRate = 500;
  m_map[0] = 0;
  m_type = type;
  
  //hh2o4 use to test, to output the WFQ state.
  NS_LOG_INFO("The queue num in initialization is: " << (uint32_t)m_packets.size() << " " << this);
  NS_LOG_INFO("The weight of WFQ is: ");
  for(uint8_t i = 0; i < m_packets.size(); i++) {
    NS_LOG_INFO(m_weight[i] << '\t');
  }
}

// zyf add for get wfq link rate.
uint64_t
H_WFQQueue::GetLinkRate ()
{
	return m_linkRate;
}

/*
 * in the distructor function, to output the final state information of WFQ to log. 
 */
H_WFQQueue::~H_WFQQueue() {
  NS_LOG_FUNCTION(this);
  NS_LOG_INFO("The queue num is: " << m_packets.size());
  NS_LOG_INFO("The weight of each queue is: ");
  for(uint8_t i = 0; i < m_weight.size(); i++) {
    NS_LOG_INFO(m_weight[i]);
  }
}

void 
H_WFQQueue::CreateQueue(void) {
  NS_LOG_FUNCTION(this);

  /**
   * create an empty inner queue, and push this empty inner queue to WFQ.
   */
  std::queue<Ptr<QueueItem> > emptyQueue;
  m_packets.push_back(emptyQueue);
  
  /**
   * inite the packet arrive time of the empty inner queue to be 0.0 s.
   */
  std::vector<double> temp_d(1, 0.0);
  m_now.push_back(temp_d);

  /**
   * init the packet size of the empty inner queue to be 0.
   */
  std::vector<uint32_t> temp_i(1, 0);
  m_size.push_back(temp_i);
  
  /**
   *  init the virtual finish time of the first packet in the empty inner queue to be -1.
   */
  m_finish.push_back(-1);
  
  /**
   * init the virtual finish time of the previous packet in the empty inner queue to be 0.0 s.
   */
  m_finish_pre.push_back(0.0);
}


void 
H_WFQQueue::CreateQueueWithWeight (uint32_t weight) {
  NS_LOG_FUNCTION(this);
  /**
   * create an empty inner queue, to do corresponding initialization.
   */
  CreateQueue();
  
  /**
   * push the value of queue weight to m_weight, which contains different weight value of inner queues.
   */
  m_weight.push_back(weight);
}

bool 
H_WFQQueue::DoEnqueue(Ptr<QueueItem> item) {
  
  NS_LOG_INFO("\n**********In the process of DoEnqueue**********\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
  NS_LOG_FUNCTION(this);

  Ptr<Packet> p = item->GetPacket();
  NS_LOG_INFO("Before remove, the packet size is: " << p->GetSize() << "\n");
 
  /**
   * resolve the packet from item
   */从item中取出报文，定义报文的ip头部以及udp头部
  Ptr<Packet> pkt = item->GetPacket()->Copy();
  
  //Ipv4Header ipHdr;
  //UdpHeader udpHdr;
  

  /**
   * \check the whether the packet has TypeTag,
   * if it has the tag, that means the packet is a data packet,
   * it will go to excute the processing of data packet.
   */
  TypeTag type;
  if(pkt->PeekPacketTag (type)) {
    
    /**
     * call function GetTosValue to get the tos value of packet.
     */
    uint32_t tosValue = GetTosValue(item);
    
    /**
     * \call function GetEnqID, to get the id of queue that the packet is supposed to be pushed in.
     * according to the tos value.
     */
    uint32_t enqID = GetEnqID(tosValue);
    NS_LOG_INFO("The value of TOS is " << tosValue << ", so the packet(ID = " << pkt->GetUid() << ") will enter the queue " << enqID);
    
    /**
     * \decide whether the queue, in which the packet is supposed to be pushed, is full.
     * if the queue is not full, then it will go into the logic below.
     */
    if (m_packets[enqID].size() < m_limit) {

      /**
       * \decide the type of node according to m_type.
       * if m_type is 1, means the node is a switch, then it will go to do the fast-control logic.
       */
      if(m_type == 1) {  
	      
        /**
         * decide whether the length of queue reaches the threshold
         */
        if(m_packets[enqID].size() * 1.0 / m_limit >= m_threshold) {
          
          /**
           * \if the length of queue reaches the threshold,
           * then defined an EcnTag.
           */
          EcnTag ecn;

          /**
           * \check whether the packet has already been attached an EcnTag.
           * if the packet has not had an EcnTag, then attach the EcnTag to the packet,
           * else do nothing, just push the packet into its queue.
           */
          if (!item->GetPacket()->PeekPacketTag(ecn)){
            
            item->GetPacket()->AddPacketTag(ecn);
            
            /**
             * \defined ip header to store the ecn packet's ip level information,
             * and output src and dst information to log, in order to check the statistic of ecn.
             */
            Ipv4Header ipHeader = GetIpv4Header (item->GetPacket());
            Ipv4Address srcIp = ipHeader.GetSource();
            Ipv4Address destIp = ipHeader.GetDestination();
            NS_LOG_INFO("Ecn packet from " << srcIp << " to " << destIp << " queueLength is " << m_packets[enqID].size());
          }
        } 
      }
      
      /**
       * push the packet both has ecn tag and doesn't has ecn tag into its queue,
       * according to the queue id that the packet is supposed to be pushed in.
       */
	    m_packets[enqID].push(item);

      /**
       * update the recording of packet size of the queue that has just pushed a packet in.
       */
	    m_size[enqID].push_back(8 * p->GetSize());

      /**
       * update the packet arrive time record of the queue that has just pushed a packet in.
       */
	    m_now[enqID].push_back(Simulator::Now().GetSeconds());
		  
      /**
       * decide whether the packet is the first one thats get into the queue.
       */
	    if (m_finish[enqID] < 0) {

        /**
         * \if the packet is the first one,
         * then calculate the virtual finish time of the packet,
         * and make to be the virtual finish time of the queue that the packet belongs to.
         */
        uint32_t totalWeight = 0;
        for (uint32_t i = 0; i < m_weight.size(); i++)
          totalWeight += m_weight[i];

        double rate = m_linkRate * double(m_weight[enqID]) / double(totalWeight);
        m_finish[enqID] = m_now[enqID].back() + (m_size[enqID][1] * 1.0 / rate);

		    NS_LOG_INFO("The first packet enqueue, the arrival time is: " << m_now[enqID].back());
	    }
    }

    /**
     * if the queue is full, then drop the packet and output the drop information to log.
     */
    else {
      NS_LOG_INFO("The queue if full -- dropping pkt");
      NS_LOG_INFO("The size of enq queue is " << m_packets[enqID].size());
      Drop(item->GetPacket());
      return false;
    }

    /**
     * \after the packet go into the queue successfully,
     * and output corresponding information to log.
     */
    Ipv4Header ipHeader;
    NS_LOG_INFO("(H_WFQQueue: " << this << ") The packet(ID = " << p->GetUid() << ", tos = " << tosValue << ") has entered the queue " << enqID << ", and the current number of packets in this queue is " << m_packets[enqID].size());
    return true;
  }

  /**
   * \if the packet has no TypeTag, that means the packet is not a data packet,
   * and it will be treated as a protocol packet,
   * then it will be pushed into a certain queue named m_control.
   */ 
  else{ 
    m_control.push(item);
	  NS_LOG_INFO("The packet has no TypeTag");
	  return true;
  }

  /**
   * in other circumstanses, the packet can't be pushed into queue,
   * then return false.
   */
  return false;
}
 
Ptr<QueueItem> 
H_WFQQueue::DoDequeue(void) {

	NS_LOG_INFO("**********In the process of DoDequeue**********");
	NS_LOG_INFO("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");

  NS_LOG_FUNCTION(this);

  /**
   * \decide whether the m_control is empty,
   * if the m_control is not empty, pop one protocol packet in m_control.
   */
	if(!m_control.empty())
	{
	  Ptr<QueueItem> item;
		item = m_control.front();
    m_control.pop();

		return item;
  }

  /**
   * \if m_control is empty, then call function GetDeqID,
   * as to get the queue id of m_packets, that suppose to pop data packet this time. 
   */
  uint32_t deqID = GetDeqID();

  Ptr<QueueItem> item;

  /**
   * \decide whether the queue that is supposed to pop data packet is empty or not.
   * if the queue is not empty, then pop a data packet out.
   */
  if (!m_packets[deqID].empty()) {
    item = m_packets[deqID].front();
    m_packets[deqID].pop();
    
    /**
     * \output corresponding information about data packet to log,
     * in order to check the dequeue situation.
     */
    uint8_t tos = GetIpv4Header (item->GetPacket()).GetTos();
    NS_LOG_INFO("In queue(" << this << "),the packet(tos = " << (uint32_t)tos << ") has departed from the queue " << deqID << " , and the current number of packets in this queue is " << m_packets[deqID].size());
  }                                                                                                                                             

  /**
   * \if the queue that supposed to pop packets is empty, that means there's no valid packet to dequeue,
   * then return an empty queue item.
   */
  else
    NS_LOG_INFO("Failed! All of the queues have been empty");

  return item;
}

bool 
H_WFQQueue::SetQueue (std::vector<uint32_t> weight) {
	NS_LOG_INFO("**********In the process of SetQueue**********");
  
	NS_LOG_INFO("At time "  << Simulator::Now().GetSeconds() << " in SetQueue start");

	NS_LOG_FUNCTION(this);
  
  /**
   * \check whether the vector weight is empty or not,
   * if weight is empty, then stop the simulation.
   */
  NS_ASSERT_MSG(weight.size() != 0, "Weight is NULL");
  
  //weight[0] indicate the num of Queue.
  uint32_t Num = weight[0];
  NS_LOG_INFO("Num = " << Num << " and the present queue num is " << m_packets.size());

  //check the queue num.if queue num is assigned to zero, assert the simulation.
  NS_ASSERT_MSG (Num != 0,"can't assign the queue num to zero,please assign a number > 0");

  //check the weight,if length of weight is not matching the queue num, assert the simulation.
  NS_ASSERT_MSG (Num == weight.size()-1, "the assigned queue num is not matching the weight num,please check the value of WeightTag before you SetQueue.");

  //check the weight value, if there exits zero or negative number in weight value, assert the simulation.
  for(uint8_t i = 1; i < weight.size(); i++) {
    NS_ASSERT_MSG(weight[i] > 0 ,"weight value can't be zero or negative,please check the parameter when using H_WFQQueue::SetQueue");
  }
  
  NS_ASSERT_MSG (Num >= m_packets.size(),"SetQueue error! Decreasing the queue num cannot be realized temporily");

	NS_LOG_INFO("Before update, the virtual finish time of packets in each queue is:");
	for(uint8_t i = 0; i < m_finish.size(); i++) {
		NS_LOG_INFO(m_finish_pre[i] << "\t" << m_finish[i]);
  }

  //hh2o4 use to test.
	NS_LOG_INFO("The origin queue num is: " << m_packets.size());
  //hh2o4 use to test.
	
  uint32_t times = Num - m_packets.size();//unify into uint32_t,or it will be a wrong num.
	for (uint32_t i = 0; i < times; i++) {
		CreateQueue();
	}

  /**
   * \it used to output the queue number and corresponding weight of each queue,
   * after the calling for SetQueue().
   */
	NS_LOG_INFO("The new queue num is: " << m_packets.size());
	NS_LOG_INFO("The origin queue weight is: ");
	for(uint8_t i = 0; i < m_weight.size(); i++) {
	  NS_LOG_INFO(m_weight[i] << "\t");
	}

  /**
   * assign the new value to m_weight
   */重新设置队列权重
	m_weight.erase (m_weight.begin(), m_weight.end());
  m_weight.assign(weight.begin()+1,weight.end());

  /**
   * \update the virtual finish time of different inner queue,
   * after updata the m_weight.
   */
	uint32_t totalWeight = 0;
	for (uint8_t i = 0; i < m_weight.size(); i++)
		 totalWeight += m_weight[i];

	for (uint8_t i = 0; i < m_finish.size(); i++) {													
		if (m_finish[i] < 0) continue;
		double rate = m_linkRate * double(m_weight[i]) / double (totalWeight);
		double start = m_now[i][1] >= m_finish_pre[i] ? m_now[i][1] : m_finish_pre[i];				
		m_finish[i] = start + (double(m_size[i][1]) / rate);												
	}
	
  NS_LOG_INFO("After update, the virtual finish time of packets in each queue is:");
	
  for(uint8_t i = 0; i < m_finish.size(); i++) {
		NS_LOG_INFO(m_finish_pre[i] << "\t" << m_finish[i]);
	}
	
  //hh2o4 use to test.
  NS_LOG_INFO("After update, the weight of each queue is:");
		
	for(uint8_t i = 0; i < m_weight.size(); i++) {
		
	  NS_LOG_INFO(m_weight[i] << "\t");
	}
	
  NS_LOG_INFO("At time "  << Simulator::Now().GetSeconds() << " in SetQueue end");
  return true;		
}

uint32_t 
H_WFQQueue::GetTosValue(Ptr<QueueItem> item) {
  
  NS_LOG_FUNCTION(this);
  
  /**
   * \get the ip header of packet, 
   * and then get the tos of packet from its ip header.
   */
  Ipv4Header ipHeader = GetIpv4Header (item->GetPacket());
  return (uint32_t)ipHeader.GetTos();
}
  
uint32_t 
H_WFQQueue::GetEnqID(uint32_t tosValue) {
  
  NS_LOG_FUNCTION(this);
	NS_LOG_INFO("*************in GetEnqID***************");
  
  //check if there's a rule to match the tosValue.
  NS_ASSERT_MSG(m_map.find(tosValue) !=  m_map.end(),"the tos value of application " << tosValue << " can't match a queue, please redefine the matching rule using H_WFQQueue::SetMap(std::map<uint8_t, uint8_t>)");
  //check if matching rule is not fit with the queue num.
  NS_ASSERT_MSG(m_map[tosValue] <= m_packets.size()-1,"want to go into queue " << (uint32_t)m_map[tosValue] << " but the maximum queue ID is " << m_packets.size()-1 <<  ", please modify the matching rule and the queue num, to make the m_map.size <= m_packets.size()");
  
  /**
   * \get the queue id that match the tos value of packet.
   */
  return m_map[tosValue];
}
  
void 
H_WFQQueue::ComputeFinishTime(uint32_t deqID) {
	NS_LOG_FUNCTION(this);

	/**
   * \check whether the queue that has just pop a packet out is empty or not,
   * if the queue is empty, then avoid to do the update of virtual finish time,
   * and return directly.
   * else, to update the virtual finish time of the queue.
   */
  if (m_now[deqID].size() <= 1)
		return;

  double start = m_now[deqID][1];
	if (start < m_finish_pre[deqID])
		start = m_finish_pre[deqID];

	uint32_t totalWeight = 0;
	for (uint32_t i = 0; i < m_weight.size(); i++)
		totalWeight += m_weight[i];

	double rate = m_linkRate * double(m_weight[deqID]) / double(totalWeight);
	m_finish[deqID] = start + (m_size[deqID][1] * 1.0 / rate);

	NS_LOG_INFO("The virtual finish time of packets in each queue is:");
	for (uint8_t i = 0; i < m_finish.size(); i++) {
		NS_LOG_INFO(m_finish_pre[i] << "\t" << m_finish[i]);
	}
}

uint32_t 
H_WFQQueue::GetDeqID(void) {
	NS_LOG_FUNCTION(this);

	/**
   * \search for the queue id that has the minimum virtual finish time,
   * firstly make the deqID be 0, if found the expected queue id, then
   * the queue id will be assigned to deqID.
   */
  uint32_t deqID = 0;
	
  /**
   * min is used to store the minimum virtual finish time.
   */
  double min = -1;

	/**
   * \travers the virtual finish time record of different inner queue,
   * then found the minimum one.
   */
  for (uint32_t i = 0; i < m_finish.size(); i++) {
		if (m_finish[i] >= 0 && (min < 0 || min > m_finish[i]))
			min = m_finish[i];
	}

  /**
   * get the deqID according to the minimum virtual finish time value.
   */
	if (min >= 0) {
		for (uint32_t i = 0; i < m_finish.size(); i++) {
			if (min == m_finish[i]) {
				deqID = i;
			  m_finish_pre[i] = m_finish[i];
				m_finish[i] = -1;
				m_now[i].erase(m_now[i].begin());
				m_size[i].erase(m_size[i].begin());
				
        /**
         * calculate the new virtual finish time of the queue whose id has just been found. 
         */
        ComputeFinishTime(i);
				break;
			}
		}
	}

	NS_LOG_INFO("the min finish time is: " << min << ", and the deqId is " << deqID);

	return deqID;
}

Ptr<const QueueItem> 
H_WFQQueue::DoPeek(void) const {
  
  NS_LOG_FUNCTION(this);
  return m_packets[0].front();
}

void
H_WFQQueue::SetMap(std::map<uint8_t,uint8_t> map) {
  m_map = map;
}

void
H_WFQQueue::SetLinkRate(uint64_t linkRate) {
  m_linkRate = linkRate;
}

void
H_WFQQueue::SetThreshold(float threshold) {
  m_threshold = threshold;
}

void
H_WFQQueue::SetLimit(uint32_t limit) {
  m_limit = limit;
}

/**
 * this funciton is used to resolve the ip header of a certain packet.
 */
Ipv4Header H_WFQQueue::GetIpv4Header (Ptr<Packet> packet)
{
  NS_LOG_FUNCTION (this);

  Ptr<Packet> copy = packet->Copy ();

  EthernetHeader csmaHeader;
  copy->RemoveHeader (csmaHeader);
  EthernetTrailer csmaTrailer;
  copy->RemoveTrailer (csmaTrailer);
            
  Ipv4Header ipHeader;
  copy->PeekHeader(ipHeader);
  return ipHeader;
}

} // namespace ns3

